package me.piebridge.douban;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.json.XML;

public class DoubanEpub {

	private ZipFile zip;
	private File file;
	private Cipher cipher;
	private ZipOutputStream epub;
	private JSONObject manifest;
	private Set<Integer> ids = new HashSet<Integer>();
	private Set<Integer> actualIds = new HashSet<Integer>();
	private List<String> contents = new ArrayList<String>();
	private Map<String, String> images = new HashMap<String, String>();
	private List<String> footnotes = new ArrayList<String>();

	public DoubanEpub(Cipher aes, ZipFile d, JSONObject m) throws IOException {
		cipher = aes;
		manifest = m;
		zip = d;
		file = new File(new File(d.getName()).getParentFile(), getEpubName(m));
		epub = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
	}

	protected String getEpubName(JSONObject manifest) {
		StringBuilder sb = new StringBuilder();
		sb.append(escapePath(manifest.optString("title")));
		JSONArray contents = manifest.optJSONArray("content");
		String authors = ((JSONObject) contents.get(0)).optString("authors").trim();
		if (authors.length() > 0) {
			sb.append("-");
			sb.append(escapePath(authors));
			if (contents.length() > 1) {
				sb.append("等");
			}
		}
		sb.append(".epub");
		return sb.toString();
	}

	public static String escapePath(String string) {
		StringBuffer sb = new StringBuffer();
		for (int i = 0, length = string.length(); i < length; i++) {
			char c = string.charAt(i);
			switch (c) {
			case '<':
			case '>':
			case '|':
			case '*':
			case '/':
			case '\\':
				break;
			case '?':
				sb.append("？");
				break;
			case ':':
				sb.append("：");
				break;
			default:
				sb.append(c);
			}
		}
		return sb.toString();
	}

	protected void addMimetype() throws IOException {
		byte[] content = "application/epub+zip".getBytes();
		ZipEntry mimetype = new ZipEntry("mimetype");
		mimetype.setMethod(ZipEntry.STORED);
		mimetype.setSize(content.length);
		CRC32 crc = new CRC32();
		crc.update(content);
		mimetype.setCrc(crc.getValue());
		epub.putNextEntry(mimetype);
		epub.write(content);
		epub.closeEntry();
	}

	protected void addContainer() throws IOException {
		// @formatter:off
		String content = "<?xml version=\"1.0\"?>\n" +
				"<container xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\" version=\"1.0\">\n" +
				"  <rootfiles>\n" +
				"    <rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n" +
				"  </rootfiles>\n" +
				"</container>";
		// @formatter:on
		epub.putNextEntry(new ZipEntry("META-INF/"));
		epub.closeEntry();
		epub.putNextEntry(new ZipEntry("META-INF/container.xml"));
		epub.write(content.getBytes());
		epub.closeEntry();
	}

	protected void addContentCSS() throws IOException {
		// @formatter:off
		String content = "sup { line-height: 0 }\n" +
				"p { line-height: 1.2em; margin-top: 0; margin-bottom: 0; min-height: 1em }\n" +
				".text { text-indent: 2em; }\n" +
				".center.text { text-indent: 0 }\n" +
				".bold { font-weight: bold }\n" +
				".left { text-align: left }\n" +
				".right { text-align: right; }\n" +
				".center { text-align: center }\n" +
				".indent { margin-left: 2em }\n" +
				".quote { font-family: 'STKaiti', 'Kaiti', serif; }\n" +
				".pagebreak { page-break-before: always }\n" +
				".footnote a { text-decoration: none }\n" +
				".content { padding-bottom: 2em }\n" +
				".title, .subtitle { margin-top: 0.4em; margin-bottom: 0.4em }\n" +
				"..authors, .translators { margin-top: 0.2em; margin-bottom: 0.2em }\n" +
				".title { font-size: x-large }\n" +
				".subtitle { font-size: large }\n" +
				".authors, .translators { font-size: small }\n" +
				"figcaption { text-align: center; font-size: small }\n" +
				"img, figcaption { max-width: 100% }\n";
		// @formatter:on
		epub.putNextEntry(new ZipEntry("OEBPS/content.css"));
		epub.write(content.getBytes());
		epub.closeEntry();

	}

	private void addAbstract() throws IOException {
		String content = manifest.optString("abstract");
		epub.putNextEntry(new ZipEntry("OEBPS/abstract.html"));
		Writer writer = new BufferedWriter(new OutputStreamWriter(epub));
		writer.write("<!DOCTYPE html>\n");
		writer.write("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n");
		writer.write("<head>\n");
		writer.write("<meta charset=\"utf-8\"/>\n");
		writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n");
		writer.write("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\"/>\n");
		writer.write("<title>简介</title>\n");
		writer.write("<style>\n");
		writer.write("p { text-indent: 2em; line-height: 1.2em; margin-top: 0; margin-bottom: 0; min-height: 1em }\n");
		writer.write(".abstract { text-align: center; font-weight: bold }\n");
		writer.write("</style>\n");
		writer.write("</head>\n");
		writer.write("<body>\n");
		writer.write("<h2 class=\"abstract\">简介</h2>\n");
		writer.write("<p>\n");
		writer.write(escape(content).replaceAll("\r", "").replaceAll("\n", "</p>\n<p>"));
		writer.write("</p>");
		writer.write("\n</body>");
		writer.write("\n</html>");
		writer.flush();
		epub.closeEntry();

	}

	private void convertContent() throws IOException {
		epub.putNextEntry(new ZipEntry("OEBPS/content.opf"));
		Writer writer = new BufferedWriter(new OutputStreamWriter(epub));
		// @formatter:off
		writer.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
				"<package xmlns=\"http://www.idpf.org/2007/opf\" version=\"3.0\" unique-identifier=\"uid\">\n" +
				"  <metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:opf=\"http://www.idpf.org/2007/opf\">\n");
		// @formatter:on
		writer.write("    <dc:title>");
		writer.write(manifest.optString("title"));
		writer.write("</dc:title>\n");
		writer.write("    <dc:language>zh</dc:language>\n");
		writer.write("    <dc:identifier id=\"uid\">");
		writer.write(manifest.optString("title"));
		writer.write("</dc:identifier>\n");
		writer.write("    <dc:creator id=\"creator\">");
		writer.write(manifest.optString("authors"));
		writer.write("</dc:creator>\n");
		writer.write("    <dc:contributor id=\"contributor\">Liu DongMiao</dc:contributor>\n");
		writer.write("    <meta property=\"dcterms:modified\">");
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd\'T\'hh:ss:mm\'Z\'");
		writer.write(sdf.format(new Timestamp(manifest.optLong("timestamp") * 1000L)));
		writer.write("</meta>\n");
		writer.write("  </metadata>\n");
		writer.write("  <manifest>\n");
		writer.write("    <item id=\"cover\" media-type=\"image/");
		writer.write(images.get("cover"));
		writer.write("\" href=\"cover.");
		writer.write(images.get("cover"));
		writer.write("\" properties=\"cover-image\"/>\n");
		writer.write("    <item id=\"toc\" media-type=\"application/xhtml+xml\" href=\"toc.html\" properties=\"nav\"/>\n");
		writer.write("    <item id=\"abstract\" media-type=\"application/xhtml+xml\" href=\"abstract.html\"/>\n");
		writer.write("    <item id=\"css\" media-type=\"text/css\" href=\"content.css\"/>\n");
		for (String name : contents) {
			writer.write("    <item id=\"");
			writer.write(name);
			writer.write("\" media-type=\"application/xhtml+xml\" href=\"");
			writer.write(name);
			writer.write(".html\"/>\n");
		}
		for (String name : images.keySet()) {
			if ("cover".equals(name)) {
				continue;
			}
			writer.write("    <item id=\"i");
			writer.write(name);
			writer.write("\" media-type=\"image/");
			writer.write(images.get(name));
			writer.write("\" href=\"");
			writer.write(name);
			writer.write(".");
			writer.write(images.get(name));
			writer.write("\"/>\n");
		}
		writer.write("  </manifest>\n");
		writer.write("  <spine>\n");
		writer.write("    <itemref idref=\"toc\"/>\n");
		writer.write("    <itemref idref=\"abstract\"/>\n");
		for (String name : contents) {
			writer.write("    <itemref idref=\"");
			writer.write(name);
			writer.write("\"/>\n");
		}
		writer.write("  </spine>\n");
		writer.write("</package>\n");
		writer.flush();
		epub.closeEntry();
	}

	public String convert() throws IOException {
		addMimetype();
		addContainer();

		JSONObject json = new JSONObject(new JSONTokener(getContent("toc")));
		JSONArray tocs = json.optJSONArray("toc");
		for (int i = 0; i < tocs.length(); ++i) {
			JSONObject toc = tocs.optJSONObject(i);
			int id = toc.optInt("paragraph_id");
			if (id > 0) {
				ids.add(id);
			}
		}

		epub.putNextEntry(new ZipEntry("OEBPS/"));
		epub.closeEntry();
		Enumeration<? extends ZipEntry> enumeration = zip.entries();
		while (enumeration.hasMoreElements()) {
			ZipEntry ze = enumeration.nextElement();
			String name = ze.getName();
			if ("toc".equalsIgnoreCase(name)) {
			} else if (name.startsWith("content")) {
			} else if (name.startsWith("image/")) {
				name = "OEBPS" + name.substring(5);
				convertImage(name, new CipherInputStream(zip.getInputStream(ze), cipher));
			} else {
				System.err.println("[error] unknown file: " + name);
			}
		}

		addAbstract();
		addContentCSS();

		JSONArray contents = manifest.optJSONArray("content");
		for (int i = 0; i < contents.length(); ++i) {
			JSONObject content = contents.getJSONObject(i);
			String name = content.optString("target");
			this.contents.add(content.optString("target"));
			convertContent(name, new CipherInputStream(zip.getInputStream(zip.getEntry(name)), cipher), content);
		}

		convertToc();
		convertContent();
		epub.close();
		zip.close();
		System.out.println("output: " + file.getAbsolutePath());
		return file.getAbsolutePath();
	}

	private void convertImage(String name, InputStream is) throws IOException {
		int length;
		byte[] bytes = new byte[8192];
		BufferedInputStream bis = new BufferedInputStream(is);
		String format = null;
		while ((length = bis.read(bytes)) != -1) {
			if (format == null) {
				if ((bytes[0] & 0xff) == 0xff && (bytes[1] & 0xff) == 0xd8 && (bytes[2] & 0xff) == 0xff) {
					format = "jpeg";
				} else if ((bytes[1] & 0xff) == 0x50 && (bytes[2] & 0xff) == 0x4e && (bytes[3] & 0xff) == 0x47) {
					format = "png";
				} else {
					System.err.println("[error] unknown image format");
					return;
				}
				epub.putNextEntry(new ZipEntry(name + "." + format));
				images.put(name.substring(6), format);
			}
			epub.write(bytes, 0, length);
		}
		epub.closeEntry();
	}

	private void convertParagraphIdIfNeeded(Writer writer, int id) throws IOException {
		if (ids.contains(id)) {
			writer.write(" id=\"p");
			writer.write(String.valueOf(id));
			writer.write("\"");
			actualIds.add(id);
		}
	}

	private void convertParagraphFormat(Writer writer, JSONObject format) throws IOException {
		if (format == null) {
			return;
		}
		List<String> keys = new ArrayList<String>();
		for (Object key : format.keySet()) {
			String attr = String.valueOf(key);
			if ("p_quote".equals(attr) || "p_bold".equals(attr) || "p_indent".equals(attr) || "p_center".equals(attr)) {
				if (format.optBoolean(attr)) {
					keys.add(attr.substring(2));
				}
			} else if ("t_indent".equals(attr)) {
				if (!format.optBoolean(attr)) {
					keys.add("text");
				}
			} else if ("p_align".equals(attr)) {
				// p_center vs p_align: center
				String value = format.optString(attr);
				if (value != null && !value.isEmpty()) {
					keys.add(value);
				}
			} else {
				System.err.println("[error] unknow format: " + attr);
			}
		}
		if (keys.size() > 0) {
			writer.write(" class=\"");
			Iterator<String> it = keys.iterator();
			while (it.hasNext()) {
				writer.write(it.next());
				if (it.hasNext()) {
					writer.write(" ");
				}
			}
			writer.write("\"");
		}
	}

	protected static String escape(String text) {
		return XML.escape(text).replaceAll("[\\u0000-\\u0008\\u000e-\\u001f]", "");
	}

	private void convertParagraphText(Writer writer, int id, Object text) throws IOException {
		if (String.class.isAssignableFrom(text.getClass())) {
			writer.write(escape((String) text));
		} else if (JSONArray.class.isAssignableFrom(text.getClass())) {
			JSONArray contents = (JSONArray) text;
			for (int i = 0; i < contents.length(); ++i) {
				JSONObject content = contents.optJSONObject(i);
				String kind = content.optString("kind");
				if ("plaintext".equals(kind)) {
					writer.write(escape(content.optString("content")));
				} else if ("emphasize".equals(kind)) {
					writer.write("<em>");
					writer.write(escape(content.optString("content")));
					writer.write("</em>");
				} else if ("strikethrough".equals(kind)) {
					writer.write("<strike>");
					writer.write(escape(content.optString("content")));
					writer.write("</strike>");
				} else if ("regular_script".equals(kind)) {
					writer.write("<i>");
					writer.write(escape(content.optString("content")));
					writer.write("</i>");
				} else if ("code".equals(kind)) {
					writer.write("<code>");
					writer.write(escape(content.optString("content")));
					writer.write("</code>");
				} else if ("footnote".equals(kind)) {
					String name = new StringBuilder().append(id).append("-").append(footnotes.size() + 1).toString();
					footnotes.add(new StringBuilder("<li class=\"footnote\" id=\"f").append(name).append("\">").append("<a href=\"#p").append(name)
							.append("\"> ^ </a><span class=\"footnote-text\">").append(escape(content.optString("content"))).append("</span></li>")
							.toString());
					writer.write("<sup id=\"p");
					writer.write(name);
					writer.write("\" class=\"footnote\"><a href=\"#f");
					writer.write(name);
					writer.write("\">[");
					writer.write(String.valueOf(footnotes.size()));
					writer.write("]</a></sup>");
				} else {
					writer.write(escape(content.optString("content")));
					System.err.println("[warning] unknown kind: " + kind);
				}
			}
		} else {
			System.err.println("[error] unknown type: " + text.getClass());
		}
	}

	private void addFootnotesIfNeeded(Writer writer) throws IOException {
		if (footnotes.size() > 0) {
			writer.write("<div class=\"footnotes\">\n");
			writer.write("<h4>注释</h4>\n");
			writer.write("<ol>\n");
			for (String footnote : footnotes) {
				writer.write(footnote);
				writer.write("\n");
			}
			writer.write("</ol>\n");
			writer.write("</div>");
			footnotes.clear();
		}
	}

	private void convertParagraph(Writer writer, String type, JSONObject content) throws IOException {
		String tag = null;
		JSONObject data;
		if ("paragraph".equals(type)) {
			if (tag == null) {
				tag = "p";
			}
		} else if ("headline".equals(type)) {
			if (tag == null) {
				tag = "h2";
			}
			writer.write("\n<");
			writer.write(tag);
			convertParagraphIdIfNeeded(writer, content.optInt("id"));
			data = content.optJSONObject("data");
			if (data.keySet().size() > 2) {
				System.err.println("[error] " + type + " cotains more keys: " + data.keySet().toString());
			}
			convertParagraphFormat(writer, data.optJSONObject("format"));
			writer.write(">");
			convertParagraphText(writer, content.optInt("id"), data.opt("text"));
			writer.write("</");
			writer.write(tag);
			writer.write(">");
		} else if ("pagebreak".equals(type)) {
			addFootnotesIfNeeded(writer);
			writer.write("\n<div");
			convertParagraphIdIfNeeded(writer, content.optInt("id"));
			writer.write(" class=\"pagebreak\">");
			writer.write("</div>");
		} else if ("illus".equals(type)) {
			JSONObject image = content.optJSONObject("data");
			writer.write("<figure class=\"");
			String layout = image.optString("layout");
			if ("L".equals(layout)) {
				writer.write("left");
			} else if ("R".equals(layout)) {
				writer.write("right");
			} else if ("C".equals(layout)) {
				writer.write("center");
			} else {
				System.err.println("[warning] unknown illus layout: " + image.optString("layout"));
			}
			writer.write("\">\n");
			writer.write("<img");
			convertParagraphIdIfNeeded(writer, content.optInt("id"));
			writer.write(" width=\"");
			writer.write(String.valueOf(image.optInt("width")));
			writer.write("\"");
			writer.write(" src=\"");
			writer.write(String.valueOf(image.optInt("seq")));
			writer.write(".");
			writer.write(images.get(image.optString("seq")));
			writer.write("\"/>");
			JSONObject legend = image.optJSONObject("full_legend");
			if (legend == null) {
				legend = image.optJSONObject("legend");
			}
			if (legend != null) {
				writer.write("<figcaption");
				writer.write(" style=\"width: ");
				writer.write(String.valueOf(image.optInt("width")));
				writer.write("px\">");
				JSONArray paragraphs = legend.optJSONObject("data").optJSONArray("paragraphs");
				for (int i = 0; i < paragraphs.length(); ++i) {
					JSONObject paragraph = paragraphs.optJSONObject(i);
					String subType = paragraph.optString("type");
					if (subType == null) {
						System.err.println("[error] illus contains null type");
					} else if (type.equals(subType)) {
						System.err.println("[error] illus cotains illus");
					} else {
						convertParagraph(writer, subType, paragraph);
					}
				}
				writer.write("</figcaption>");
			}
			writer.write("</figure>\n");
		} else {
			System.err.println("[error] unknown type: " + type);
		}
	}

	private String getContent(String name) throws IOException {
		ZipEntry ze = zip.getEntry(name);
		if (ze == null) {
			System.err.println("[error] no table of contents");
			return "";
		}
		return Files.readAsString(zip.getInputStream(ze));
	}

	private void convertToc() throws IOException {
		JSONObject json = new JSONObject(new JSONTokener(getContent("toc")));
		JSONArray tocs = json.optJSONArray("toc");

		epub.putNextEntry(new ZipEntry("OEBPS/toc.html"));
		Writer writer = new BufferedWriter(new OutputStreamWriter(epub));
		writer.write("<!DOCTYPE html>\n");
		writer.write("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n");
		writer.write("<head>\n");
		writer.write("<meta charset=\"utf-8\"/>\n");
		writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n");
		writer.write("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\"/>\n");
		writer.write("<title>目录</title>\n");
		writer.write("<style>\n");
		writer.write(".toc { text-align: center; font-weight: bold }\n");
		writer.write("li { list-style-type: none }\n");
		writer.write("</style>\n");
		writer.write("</head>\n");
		writer.write("<body>\n");
		writer.write("<nav epub:type=\"toc\">\n");
		writer.write("<h2 class=\"toc\">目录</h2>\n");
		writer.write("<ol>");
		int lastLevel = -1;
		for (int i = 0; i < tocs.length(); ++i) {
			JSONObject toc = tocs.optJSONObject(i);
			int partNo = toc.optInt("part_no");
			String title = escape(toc.optString("title"));
			if (title.length() == 0) {
				System.err.println("[warning] empty title in toc");
				continue;
			}
			int id = toc.optInt("paragraph_id");
			int level = toc.optInt("level");
			if (level > 1) {
				System.err.println("[warning] unsupported 2+ level " + level);
			}
			if (id > 0 && !actualIds.contains(id)) {
				System.err.println("[warning] non-exist paragraph " + id + ", title: " + title);
				continue;
			}
			if (lastLevel == -1) {
			} else if (level < lastLevel) {
				writer.write("</li>");
				writer.write("\n</ol>");
				writer.write("\n</li>");
			} else if (level > lastLevel) {
				writer.write("\n<ol>");
			} else {
				writer.write("</li>");
			}
			writer.write("\n<li>");
			writer.write("<a href=\"content");
			writer.write(String.valueOf(partNo));
			writer.write(".html");
			if (id > 0) {
				writer.write("#p");
				writer.write(String.valueOf(id));
			}
			writer.write("\">");
			writer.write(title);
			writer.write("</a>");
			lastLevel = level;
		}
		while (lastLevel >= 0) {
			writer.write("</li>");
			writer.write("\n</ol>");
			--lastLevel;
		}
		writer.write("\n</nav>");
		writer.write("\n</body>");
		writer.write("\n</html>");
		writer.flush();
		epub.closeEntry();
	}

	private void convertContent(String name, InputStream is, JSONObject manifest) throws IOException {
		JSONObject json = new JSONObject(new JSONTokener(Files.readAsString(is)));
		String title = json.optString("title");
		JSONArray contents = json.getJSONArray("contents");
		epub.putNextEntry(new ZipEntry("OEBPS/" + name + ".html"));
		Writer writer = new BufferedWriter(new OutputStreamWriter(epub));
		writer.write("<!DOCTYPE html>\n");
		writer.write("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n");
		writer.write("<head>\n");
		writer.write("<meta charset=\"utf-8\"/>\n");
		writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n");
		writer.write("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\"/>\n");
		writer.write("<link href=\"content.css\" rel=\"stylesheet\"/>\n");
		writer.write("<title>");
		writer.write(title);
		writer.write("</title>\n");
		writer.write("</head>\n");
		writer.write("<body>\n");
		writer.write("<div class=\"content\">\n");
		writer.write("<h3 class=\"title\">");
		writer.write(manifest.optString("title"));
		writer.write("</h3>\n");
		String subtitle = manifest.optString("sub_title");
		if (subtitle != null && subtitle.length() > 0) {
			writer.write("<h3 class=\"subtitle\">");
			writer.write(subtitle);
			writer.write("</h3>\n");
		}
		String authors = manifest.optString("authors");
		if (authors != null && authors.length() > 0) {
			writer.write("<h4 class=\"authors\">");
			writer.write(authors.trim());
			writer.write("</h4>\n");
		}
		String translators = manifest.optString("translators");
		if (translators != null && translators.length() > 0) {
			writer.write("<h4 class=\"translators\">");
			writer.write(translators.trim());
			writer.write(" 译");
			writer.write("</h4>\n");
		}
		writer.write("</div>");
		for (int i = 0; i < contents.length(); ++i) {
			JSONObject content = contents.optJSONObject(i);
			String type = content.optString("type");
			if (type != null && !type.isEmpty()) {
				convertParagraph(writer, type, content);
			} else {
				System.err.println("[error] unknown type: " + type + ", content: " + content.toString());
			}
		}
		addFootnotesIfNeeded(writer);
		writer.write("\n</body>");
		writer.write("\n</html>");
		writer.flush();
		epub.closeEntry();
	}
}
